/** * Media manager for frontend file-related fields. * * @since 2.2 * @package CRED */ var Toolset = Toolset || {}; Toolset.CRED = Toolset.CRED || {}; Toolset.CRED.Frontend = Toolset.CRED.Frontend || {}; var credFrontEndViewModel = { /** * @description JSON object that holds AJAX messages to be sent when forms are fully initialized to prevent getting responses before observable bindings are made. */ messagesQueue: {}, /** * @description Array of Toolset Forms present in the currnet page */ credForms: [], /** * @description a list of Toolset Form IDs that's ready for third-party actions */ readyCREDForms: [], /** * @description Loading Spinner class */ loadingSpinnerClass: "loading-spinner", /** * @description Loading spinner image for ajax forms */ loadingSpinnerImagePath: "/wp-admin/images/wpspin_light-2x.gif", /** * @description Recaptcha widget id list for each cred form belongs to */ recaptcha_widget_ids: [], /** * Reload and init reCaptha component after a cred form ajax call * * @param $current_form * * @since 1.9.3 */ tryToReloadReCAPTCHA: function ($current_form) { if (typeof grecaptcha !== 'undefined') { var formID = $current_form.attr('id'); var $recaptcha_selector = $current_form.find('div.g-recaptcha'); if ($recaptcha_selector.length) { var _sitekey = $recaptcha_selector.data('sitekey'); if (typeof _sitekey !== 'undefined') { var recaptcha_widget_id = grecaptcha.render($recaptcha_selector.attr('id'), {sitekey: _sitekey}); //init current recaptcha widget id if (typeof this.recaptcha_widget_ids[formID] === 'undefined') { this.recaptcha_widget_ids[formID] = []; } this.recaptcha_widget_ids[formID] = recaptcha_widget_id; } } } }, /** * Show and Hide the ReCAPTHA error messsage at run time * * @param $form * * @returns boolean * * @since 1.9.3 */ handleReCAPTCHAErrorMessage: function ($form) { if (typeof grecaptcha !== 'undefined') { var $error_selector = $form.find('div.recaptcha_error'); var formID = $form.attr('id'); if (typeof this.recaptcha_widget_ids[formID] !== 'undefined') { if (grecaptcha.getResponse(this.recaptcha_widget_ids[formID]) == '') { $error_selector.show(); setTimeout(function () { $error_selector.hide(); }, 5000); return false; } else { //reset recapatcha widget_id this.recaptcha_widget_ids[formID] = undefined; } } $error_selector.hide(); } return true; }, /** * Set disabled the submit button * * @param $form * * @since 1.9.3 */ disableSubmitForm: function (formID, isValidForm, credSettings) { var $form = jQuery(formID); if (isValidForm) { $form.find( '.wpt-form-submit' ).prop( 'disabled', true ); } }, /** * Enable the submit form * * @param $form * * @since 1.9.3 */ enableSubmitForm: function ($form) { $form.find('.wpt-form-submit').prop('disabled', false); }, /** * @type {bool} */ isWpEditorAvailable: null, /** * Check whether wp.editor is available * * @return {bool} */ checkWpEditorAvailable: function() { if ( null == this.isWpEditorAvailable ) { this.isWpEditorAvailable = ( _.has( window, 'wp' ) && _.has( window.wp, 'editor' ) && _.has( window.wp.editor, 'remove' ) && _.has( window.wp.editor, 'initialize' ) ); } return this.isWpEditorAvailable; }, /** * Reload and re-init tinyMCE after a cred form ajax call * * @param {object} $container Node to reload inner tinyMCE editors * * @since 1.9.3 */ reloadTinyMCE: function( $container ) { $container = ( typeof $container !== 'undefined' ) ? $container : jQuery( document ); var currentInstance = this; jQuery( 'textarea.wpt-wysiwyg', $container ).each( function( index ) { var $area = jQuery( this ), area_id = $area.prop('id'); if ( currentInstance.checkWpEditorAvailable() ) { // WordPress over 4.8, hence wp.editor is available and included wp.editor.remove( area_id ); var tinymceSettings = ( _.has( window, 'tinyMCEPreInit' ) && _.has( window.tinyMCEPreInit, 'mceInit' ) && _.has( window.tinyMCEPreInit.mceInit, area_id ) ) ? window.tinyMCEPreInit.mceInit[ area_id ] : true, qtSettings = ( _.has( window, 'tinyMCEPreInit' ) && _.has( window.tinyMCEPreInit, 'qtInit' ) && _.has( window.tinyMCEPreInit.qtInit, area_id ) ) ? window.tinyMCEPreInit.qtInit[ area_id ] : true, hasMediaButton = ! jQuery( 'textarea#' + area_id ).hasClass( 'js-toolset-wysiwyg-skip-media' ), hasToolsetButton = ! jQuery( 'textarea#' + area_id ).hasClass( 'js-toolset-wysiwyg-skip-toolset' ), mediaButtonsSettings = ( hasMediaButton || hasToolsetButton ); wp.editor.initialize( area_id, { tinymce: tinymceSettings, quicktags: qtSettings, mediaButtons: mediaButtonsSettings } ); if ( mediaButtonsSettings ) { var $mediaButtonsContainer = jQuery( '#wp-' + area_id + '-wrap .wp-media-buttons' ); $mediaButtonsContainer.attr( 'id', 'wp-' + area_id + '-media-buttons' ); if ( ! hasMediaButton ) { $mediaButtonsContainer.find( '.insert-media.add_media' ).remove(); } if ( hasToolsetButton ) { /** * Broadcasts that the WYSIWYG field initialization was completed, * only if the WYSIWYG field should include Toolset buttons. * * @param {string} area_id The underlying textarea id attribute * * @event toolset:forms:wysiwygFieldInited * * @since 2.1.2 */ jQuery( document ).trigger( 'toolset:forms:wysiwygFieldInited', [ area_id ] ); } } } else { // WordPress below 4.8, hence wp-editor is not available // so we turn those fields into simple textareas jQuery( '#wp-' + area_id + '-editor-tools' ).remove(); jQuery( '#wp-' + area_id + '-editor-container' ) .removeClass( 'wp-editor-container' ) .find( '.mce-container' ) .remove(); jQuery( '#qt_' + area_id + '_toolbar' ).remove(); jQuery( '#' + area_id ) .removeClass( 'wp-editor-area' ) .show() .css( { width: '100%' } ); } }); }, /** * Manage the form submission when validation succeded: * - If it an AJAX form, submit it. * - if it is not an AJAX form, disable the submit button while the form reloads the page. * * @param formID * @param isAjaxForm * @param credSettings * * @since 1.9.3 * @since 2.4 Manage both AJAX and non AJAX submission for valid forms */ onValidatedSubmitForm: function( formID, isAjaxForm, credSettings ) { var thiz = this; var $form = jQuery( formID ); var site_url = credSettings.site_url; if ( isAjaxForm ) { jQuery( '' ).attr( 'type', 'hidden' ).appendTo( formID ); thiz.startLoading( $form.find( '.wpt-form-submit' ), site_url ); if ( _.has( window, 'tinyMCE' ) && _.has( window.tinyMCE, 'triggerSave' ) ) { // This will refresh the value of all tinyMCE instances of the page: // better too much than too little! window.tinyMCE.triggerSave(); } jQuery( formID ).ajaxSubmit({ url: cred_frontend_i18n.ajaxurl, data: { action: cred_frontend_i18n.submit.action, wpnonce: cred_frontend_i18n.submit.nonce, lang: cred_frontend_i18n.lang }, dataType: 'json', success: function( response ) { $form.replaceWith( response.data.output ); if ( 'ok' === response.data.result ) { /** * The AJAX form was successfully submitted. * * @param string formID * @since 1.9.3 */ Toolset.hooks.doAction( 'cred_form_ajax_success', formID ); } else { /** * The AJAX form failed to submit. * * @param string formID * @since 1.9.3 */ Toolset.hooks.doAction( 'cred_form_ajax_error', formID ); } }, error: function() { /** * The AJAX form failed to submit. * * @param string formID * @since 1.9.3 */ Toolset.hooks.doAction( 'cred_form_ajax_error', formID ); }, complete: function (response) { thiz.stopLoading(); /** * The AJAX form submission was completed, either successfully or failing. * * @param string formID * @since 1.9.3 */ Toolset.hooks.doAction( 'cred_form_ajax_completed', formID ); } }); } else { // Non AJAX form already validated and ready to be submitted: // flag the submit button so it can not trigger the process again // and we avoid multiple form submission on fast clicks. $form .find( '.wpt-form-submit' ) .addClass( 'js-wpt-form-submitting' ); } }, /** * Append wp native spinner next to a selector (by defualt is submit button) * * @param $selector_to_append * @param site_url * * @since 1.9.3 */ startLoading: function( $selector_to_append, site_url ) { var $body = jQuery("body"); $body.addClass("wpt-loading"); var loading_icon = site_url + this.loadingSpinnerImagePath; $selector_to_append.after(''); }, /** * @since 1.9.3 */ stopLoading: function () { var $body = jQuery("body"); $body.removeClass("wpt-loading"); jQuery('.' + this.loadingSpinnerClass).remove(); }, /** * Function called when an Ajax Form is validated and submitted * * @param formID * * @since 1.9.3 */ onAjaxFormSubmit: function( formID ) { $form_selector = jQuery(formID); this.enableSubmitForm($form_selector); this.initColorPicker($form_selector); //Reset initialised validation forms if (window.hasOwnProperty('initialisedCREDForms')) { initialisedCREDForms = []; } //Reapply bindings for the form this.applyViewModelBindings(); this.activatePreviewMode(); this.reloadTinyMCE( $form_selector ); this.tryToReloadReCAPTCHA($form_selector); }, /** * @description Updates Toolset Forms auto-draft post ID */ updateFormsPostID: function () { this.getAllForms(); for (var single_form in this.credForms) { single_form = this.credForms[single_form]; var form_data = this.extractFormData(single_form); this.assignDynamicObservableID(form_data); } }, /** * @description Returns all Toolset Forms in document */ getAllForms: function () { var document_forms = jQuery('.cred-form, .cred-user-form', document); for (var form_index in document_forms) { if (isNaN(form_index)) { break; } this.credForms.push(document_forms[form_index]); } return this.credForms; }, /** * @description Assigns an observable binding ID to each Toolset Form to be updated dynamically when observable value changes. * @param form_data JSON object returned from extractFormData method */ assignDynamicObservableID: function (form_data) { if (form_data.post_id_node !== undefined && form_data.post_id_node !== null) { form_data.binding_property_name = "post_id_observable_" + this.uniqueID() + this.uniqueID(); this[form_data.context_id][form_data.binding_property_name] = ko.observable(form_data.post_id); this[form_data.context_id][form_data.binding_property_name + "_submit"] = ko.computed(function () { return (this[form_data.context_id][form_data.binding_property_name]() === undefined); }, this); jQuery(form_data.post_id_node).attr('data-bind', 'value: ' + form_data.binding_property_name); jQuery(form_data.form_submit_node).attr('disabled', 'disabled'); var cred_check_id_ajax_data = { action: 'check_post_id', post_id: form_data.post_id, form_id: form_data.form_id, binding_property_name: form_data.binding_property_name, form_index: form_data.binding_property_name, form_context_id: form_data.context_id }; this.messagesQueue[form_data.binding_property_name] = cred_check_id_ajax_data; } }, /** * @description Returns a JSON object with useful form information including form_id, auto-draft post_id, the hidden input where auto-draft post_id is saved, and the submit button for the form. * @param form HTML form node */ extractFormData: function (form) { //Setup a knockout context for each form var form_context_binding_id = 'cred_form_context_' + this.uniqueID(); this[form_context_binding_id] = {}; jQuery(form).attr('data-bind', 'with: ' + form_context_binding_id); return { form_id: (jQuery(form).children("input[name='_cred_cred_prefix_form_id']") ? jQuery(form).children("input[name='_cred_cred_prefix_form_id']").val() : null), post_id: (jQuery(form).children("input[name='_cred_cred_prefix_post_id']") ? jQuery(form).children("input[name='_cred_cred_prefix_post_id']").val() : null), post_id_node: jQuery(form).children("input[name='_cred_cred_prefix_post_id']"), form_submit_node: jQuery(form).children('.wpt-form-submit'), context_id: form_context_binding_id }; }, /** * @description Returns a uniqueID */ uniqueID: function () { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); }, /** * @description Sends out all messages in the messagesQueue via AJAX */ initQueue: function () { var queue_keys = Object.keys(this.messagesQueue); if (queue_keys.length > 0) { for (var key in queue_keys) { var message = this.messagesQueue[queue_keys[key]]; jQuery.post(cred_frontend_i18n.ajaxurl, message, function (callback_data) { if (callback_data != "" && callback_data != 0) { try { var callback_data = JSON.parse(callback_data); credFrontEndViewModel[callback_data.form_context_id][callback_data.observable_id](callback_data.pid); } catch (err) { console.error('Toolset Forms: Error parsing callback data for `check_post_id` '); } } }); } } }, /** * @description Adds IDs for both labels and inputs for accessibility support * @since 1.8.6 */ addAccessibilityIDs: function () { var $cred_form_labels = jQuery('.cred-form .form-group label'); for (var form_label_index in $cred_form_labels) { if (isNaN(form_label_index)) { break; } var $form_label = jQuery($cred_form_labels[form_label_index]); var accessibility_id = this.uniqueID(); $input_array = []; $input_array.push($form_label.parent().find(':input:not(:button)')); $input_array.push($form_label.parent().find('select')[0]); $input_array.push($form_label.parent().find('textarea')[0]); if ($input_array.length > 0) { for (var input in $input_array) { if ($input_array[input] !== undefined) { $input_array[input] = jQuery($input_array[input]); if ($input_array[input].attr('id') !== undefined && $input_array[input].attr('id') !== null && $input_array[input].attr('id') != "") { $form_label.attr('for', $input_array[input].attr('id')); } else { $input_array[input].attr('id', accessibility_id); $form_label.attr('for', accessibility_id); } } } } } }, /** * @description Cleans all Toolset Form nodes and apply the bindings * @since 1.9 */ applyViewModelBindings: function () { //Clear initialised Toolset Form, only from KO bindings and not from custom ones this.readyCREDForms = []; for (var cred_form in this.credForms) { var original = ko.utils.domNodeDisposal['cleanExternalData']; ko.utils.domNodeDisposal['cleanExternalData'] = function () { }; ko.cleanNode(this.credForms[cred_form]); ko.utils.domNodeDisposal['cleanExternalData'] = original; ko.applyBindings(this, this.credForms[cred_form]); var cred_form_id = jQuery(this.credForms[cred_form]).attr('id'); this.readyCREDForms.push(cred_form_id); jQuery('.js-wpt-validate', '#' + cred_form_id).removeClass('js-wpt-validate'); jQuery(document).trigger('cred_form_ready', { form_id: cred_form_id }); } }, /** * @description Disables file inputs while the form is in preview mode * @since 1.9 */ activatePreviewMode: function () { //disable media buttons in preview mode if (window.hasOwnProperty('cred_form_preview_mode') && window.cred_form_preview_mode == true) { jQuery('#insert-media-button').prop('disabled', true); jQuery('.insert-media').prop('disabled', true); jQuery('.cred-form input[type="file"]').attr('onclick', 'return false'); jQuery('.cred-user-form input[type="file"]').attr('onclick', 'return false'); jQuery(document).on('toolset_repetitive_field_added', function () { jQuery('input[type="file"]', $parent).attr('onclick', 'return false'); jQuery('input[type="file"]', $parent).attr('onclick', 'return false'); }); } }, /** * Init Color Picker * * @param $form */ initColorPicker: function ($form) { if (typeof(wptColorpicker) !== 'undefined') { wptColorpicker.init($form); } } }; (function () { //Add observable IDs and prepare messages queue credFrontEndViewModel.updateFormsPostID(); //Apply bindings and init ajax requests to update forms setTimeout(function () { var isPreview = jQuery('#lbl_preview').length > 0; if (!isPreview) { credFrontEndViewModel.applyViewModelBindings(); } }, 200); setTimeout(function () { credFrontEndViewModel.initQueue(); }, 300); jQuery(function () { credFrontEndViewModel.addAccessibilityIDs(); credFrontEndViewModel.activatePreviewMode(); /** * @description: JS code to fix attachment post_id when media upload (up to wysiwyg fields) is opened * Each media attached will referrer to the post_id created by the cred form * @since 1.9.1 */ jQuery(document.body).on('click', 'form.cred-form .wp-media-buttons > .button.insert-media.add_media, form.cred-user-form .wp-media-buttons > .button.insert-media.add_media', function () { if (wp && wp.hasOwnProperty('media')) { var $current_form = jQuery(this).closest('form'); var current_cred_form_post_id = jQuery("input[name='_cred_cred_prefix_post_id']", $current_form).val(); if ($current_form && current_cred_form_post_id && wp.media.model.settings.post.id !== current_cred_form_post_id ) { wp.media.model.settings.post.id = current_cred_form_post_id; } } }); }); //Once the cred form is ready jQuery(document).on('cred_form_ready', function (evt, form_data) { var $form = jQuery("#" + form_data.form_id); //uncheck generic checkboxes jQuery('input[type="checkbox"][cred_generic="1"]').each(function (index, checkbox) { if (jQuery(checkbox).attr('default_checked') != 1) { jQuery(checkbox).prop('checked', false); } else { jQuery(checkbox).prop('checked', true); } }); //Queue after conditional and validation init setTimeout(function () { jQuery('.form-submit', $form).attr('disabled', false); }, 4); credFrontEndViewModel.initColorPicker($form); $form.on('submit', function () { //If recaptcha is not valid stops the submit if (!credFrontEndViewModel.handleReCAPTCHAErrorMessage(jQuery(this))) { return false; } }); }); jQuery( document ).on( 'js_event_wpv_pagination_completed js_event_wpv_parametric_search_results_updated', function( event, data ) { jQuery( '.cred-form, .cred-user-form', data.layout ).each( function() { var $form = jQuery( this ); credFrontEndViewModel.initColorPicker( $form ); credFrontEndViewModel.reloadTinyMCE( $form ); credFrontEndViewModel.tryToReloadReCAPTCHA( $form ); }); }); /** * Halt multiple clicks on validated non AJAX forms, * by halting the submit button click. * * @since 2.4 */ jQuery( document ).on( 'click', '.js-wpt-form-submitting', function( e ) { e.preventDefault(); }); //bounding onAjaxFormSubmit var boundOnAjaxFormSubmit = _.bind(credFrontEndViewModel.onAjaxFormSubmit, credFrontEndViewModel); //After Ajax submit form call is completed (with success or error) Toolset.hooks.addAction('cred_form_ajax_completed', boundOnAjaxFormSubmit); //bounding onValidatedSubmitForm var boundOnValidatedSubmitForm = _.bind(credFrontEndViewModel.onValidatedSubmitForm, credFrontEndViewModel); //After cred form submit validation success Toolset.hooks.addAction('toolset-form-onsubmit-validation-success', boundOnValidatedSubmitForm); //bounding boundDisableSubmitForm var boundDisableSubmitForm = _.bind(credFrontEndViewModel.disableSubmitForm, credFrontEndViewModel); //If Form is ajax disable submit button on toolset-ajax-submit event Toolset.hooks.addAction('toolset-ajax-submit', boundDisableSubmitForm); })(); //Method recaptcha callback var onLoadRecaptcha = function () { //Init of all recaptcha jQuery.each(jQuery('.g-recaptcha'), function (i, recaptcha_selector) { var $current_form = jQuery(recaptcha_selector).closest('form'); credFrontEndViewModel.tryToReloadReCAPTCHA($current_form); }); }; /** * Manager for frontend delete links. */ Toolset.CRED.Frontend.Delete = function( $ ) { var self = this; self.i18n = cred_frontend_i18n; self.selector = '.js-cred-delete-post'; $( document ).on( 'click', self.selector, function( e ) { e.preventDefault(); var $handle = $( this ), ajaxData = { action: self.i18n.deletePost.action, wpnonce: self.i18n.deletePost.nonce, credPostId: $handle.data( 'postid' ), credAction: $handle.data( 'action' ), credOnSuccess: $handle.data( 'onsuccess' ) }; var $spinner = $( '' ); $handle.replaceWith( $spinner ); $.ajax({ url: self.i18n.ajaxurl, data: ajaxData, dataType: 'json', type: "POST", success: function( originalResponse ) { var response = WPV_Toolset.Utils.Ajax.parseResponse( originalResponse ); if ( response.success ) { self.doSuccess( response.data.onsuccess, $spinner ); } else { self.doError( $spinner ); } }, error: function ( ajaxContext ) { self.doError( $spinner ); } }); }); /** * Perform the success action after deleting. * * @param string onsuccess Action to perform. * @param $handle Reference to the object to act upon, if needed. */ self.doSuccess = function( onsuccess, $handle ) { switch ( onsuccess ) { case 'self': window.location.reload( true ); break; case 'none': case '': $handle.hide(); break; default: window.location = onsuccess; break; } } /** * Perform the error action after deleting failed. * * @param $handle Reference to the object to act upon. */ self.doError = function( $handle ) { var $error = $( '' + self.i18n.deletePost.messages.error + '' ); $handle.replaceWith( $error ); } }; jQuery( function(){ Toolset.CRED.Frontend.deleteInstance = new Toolset.CRED.Frontend.Delete( jQuery ); });